/**
   Realiza peticiones automaticas sobre el servidor HTTP de operaciones aritmeticas.

   La cadencia de envio de mensajes se establece mediante un temporizador.
   Los operadores se calculan de forma aleatoria.

   El servidor de este cliente: http_server.p o http_rserver.p
*/

#include <iostream>

#include <string.h>
#include <unistd.h>

#include <nemesis.h>
#include <nemesis.app.functions.h>
#include <nemesis.comm.h>

#include <nemesis.timex.TimeController.h>
#include <nemesis.timex.Clock.h>

#include <nemesis.http.Request.h>
#include <nemesis.http.Response.h>
#include <nemesis.http.Transport.h>
#include <nemesis.http.Handler.h>

#include <test.Response.h>
#include <test.Request.h>

class Sender : public timex::Clock {
public:
   Sender () : Clock ("Sender", 1000), 
      a_messageBySecond (0), 
      a_nquarter (0), 
      a_errorCounter (0),
      a_txMessageCounter (0)
   {
      a_httpRequest.setMethod (http::Method::Type::Post);
      a_httpRequest.setURI ("HTTPKClient");
   }

   void setMessageBySecond (const int messageBySecond) throw () { a_messageBySecond = messageBySecond; }

   int getTxMessageCounter () const throw () { return a_txMessageCounter; }

private:
   int a_messageBySecond;
   int a_nquarter;
   int a_errorCounter;
   int a_txMessageCounter;
   http::Request a_httpRequest;
   test::Request a_testRequest;

   void tick () throw (RuntimeException);
};

class MyHandler : public http::Handler {
public:
   MyHandler () : http::Handler ("http_client::MyHandler") {;}

private:
   http::Response a_httpResponse;
   test::Response a_testResponse;

   void evRequest (ClientSocket&, const http::Request&) throw (RuntimeException) {;}
   void evResponse (ClientSocket&, const http::Response&) throw (RuntimeException);
   
   static bool isOk (const test::Response& response) throw ();
};

class MyCommunicator : public Communicator {
public:
   MyCommunicator () : Communicator (), a_avgResponseTime (0), a_rxMessageCounter (0) {;}

   void count (const Milisecond delay) throw (RuntimeException);

private:
   int a_avgResponseTime;
   int a_rxMessageCounter;
   MyHandler a_httpHandler;

   void eventReceiveMessage (ClientSocket&, const Message&) throw (RuntimeException);

   void eventBreakConnection (const ClientSocket&) throw ();

   void eventBreakConnection (Server* server) throw () {
      comm::Communicator::eventBreakConnection (server);
   }
   void eventBreakConnection (const Service* service) throw () { 
      comm::Communicator::eventBreakConnection (service);
   }   
};

class HeavyClient : public nemesis::comm::Application {
public:
   HeavyClient ();

   Server* getServer () const throw () { return a_server; }
   const Sender* getSender () const throw () { return &a_sender; }

private:
   MyCommunicator a_communicator;
   timex::TimeController a_timeController;
   Sender a_sender;
   Server* a_server;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
};

using namespace std;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   HeavyClient app;

   srand (time (NULL));

   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Information);
      string traceFile ("client.");
      traceFile += nemesis::functions::asString ((int) getpid ());
      traceFile += ".trace";
      Logger::initialize ("http_client", new TraceWriter (traceFile.c_str (),4096000));

      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }

   return 0;
}

HeavyClient::HeavyClient () :
   Application ("http_client", "Cliente HTTP", "1.0"),
   a_communicator (),
   a_timeController (1000, 250)
{
   CommandLine& commandLine (CommandLine::instanciate ());

   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que el servidor atiende respuestas.");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP Puerto en el que el servidor atiende respuestas.");
   commandLine.add ("n", CommandLine::Argument::Mandatory, "Numero de mensajes por segundo");
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
}

void HeavyClient::initialize ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   Network& network = Network::instanciate ();

   a_server = network.createServer (cl.getValue ("a"), cl.getIntegerValue ("p"), true, &http::Transport::getFactory ());
   a_sender.setMessageBySecond (cl.getIntegerValue ("n"));

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));
}

void HeavyClient::run ()
   throw (RuntimeException)
{
   a_timeController.activate (a_sender);

   a_communicator.accept ();
}

void MyCommunicator::eventReceiveMessage (ClientSocket& clientSocket, const Message& message)
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("MyCommunicator", "eventReceiveMessage", FILE_LOCATION));

   if (clientSocket.support (http::Transport::className ()) == false)
      return;

   a_httpHandler.apply (clientSocket, message);
}

void MyCommunicator::count (const Milisecond delay)
   throw (RuntimeException)
{
   Guard guard (this, "MyCommunicator::eventReceiveMessage");
   
   a_rxMessageCounter ++;
   a_avgResponseTime += delay;
}

void MyCommunicator::eventBreakConnection (const ClientSocket& clientSocket)
   throw ()
{
   if (a_rxMessageCounter == 0)
      return;

   LOGNOTICE (
      HeavyClient& app = static_cast <HeavyClient&> (nemesis::app::functions::getApp ());   
      string msg ("Tiempo medio respuesta: ");
      msg += nemesis::functions::asString (a_avgResponseTime / a_rxMessageCounter);
      msg += " ms";
      msg += nemesis::functions::asText (" | Rx: ", a_rxMessageCounter);
      msg += nemesis::functions::asText (" | Tx: ", app.getSender ()->getTxMessageCounter ());
      Logger::notice (msg, FILE_LOCATION);

      cout << msg << endl << endl;
   );
   requestStop ();
   comm::Communicator::eventBreakConnection (clientSocket);
}

void Sender::tick ()
   throw (RuntimeException)
{
   Server* server = static_cast <HeavyClient&> (nemesis::app::functions::getApp ()).getServer ();
   Communicator* communicator = nemesis::app::functions::component <Communicator> (FILE_LOCATION);

   if (a_errorCounter > 100) {
      communicator->requestStop ();
      Logger::warning ("Terminado por errores continuos en la conexion", FILE_LOCATION);
      return;
   }

   for (int n = 0; n < a_messageBySecond && communicator->hasRequestedStop () == false; n ++) {
      a_testRequest.op = '+';
      a_testRequest.x = rand () % 1000;
      a_testRequest.y = rand () % 1000;
      a_testRequest.initTime = nemesis::functions::milisecond ();

      try {
         a_httpRequest.setBody (a_testRequest.code ());
         server->send (a_httpRequest);
         a_txMessageCounter ++;
      }
      catch (RuntimeException& ex) {
         a_errorCounter ++;
         ex.trace ();
         break;
      }
   }
}

void MyHandler::evResponse (ClientSocket& clientSocket, const http::Response& response)
   throw (RuntimeException)
{
   if (response.getStatusCode () == 200) {
      a_testResponse.decode (response.getBody ());
      
      test::Response& response (a_testResponse);
      
      const nemesis::Milisecond now = nemesis::functions::milisecond ();
      const int delay =  now - (Milisecond) response.initTime;

      if (delay > 0 && isOk (response) == true) {
         app::functions::component <MyCommunicator> (FILE_LOCATION)->count (delay);

         LOGINFORMATION (
            string msg = nemesis::functions::asString (
               "%d %c %d = %d", response.x, response.op, response.y, response.result
            );
            msg += nemesis::functions::asText (" | Delay: ", delay);
            Logger::information (msg, FILE_LOCATION);
         );
      }
      else {
         LOGWARNING (
            string msg = nemesis::functions::asString (
               "Flip: %d %c %d = %d", response.x, response.op, response.y, response.result
            );
            msg += nemesis::functions::asText (" | Message: ", response.getBody ());
            msg += nemesis::functions::asText (" | Delay: ", delay);
            Logger::warning (msg, FILE_LOCATION);
         );
      }
   }
}

bool MyHandler::isOk (const test::Response& response) 
   throw ()
{
   if (response.op != '+' && response.op != '-' && response.op != '*' && response.op != '/') 
      return false;
      
   int result = 0;
   
   switch (response.op) {
      case '+':
         result = response.x + response.y;
         break;
      case '-':
         result = response.x - response.y;
         break;
      case '*':
         result = response.x * response.y;
         break;
      case '/':
         result = (response.y != 0) ? (response.x / response.y): 0;
         break;
   }
    
   return result == response.result;   
}
